package controllers;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.persistence.PersistenceException;
import javax.persistence.Query;

import play.*;
import play.mvc.*;
import models.*;
import play.data.validation.*;
import play.db.jpa.JPA;
import play.db.jpa.GenericModel.JPAQuery;

@Check("employee")
@With(Secure.class)
public class EmployeeArea extends Controller {
	public static void index() {
        if(Security.isConnected()) {
			Employee employee = Employee.find("byUsername", Security.connected()).first();
            renderArgs.put("username", employee.username);
			renderArgs.put("first_name", employee.firstname);
			renderArgs.put("last_name", employee.lastname);			
        }
		
		//Pull out customer details for customer lookup list
		List<Customer> allCustomers = Customer.find("order by username asc, firstname asc").fetch();
		String customerLookupList = "";
		for(int i=0; i<allCustomers.size(); i++){
			customerLookupList += "'" + allCustomers.get(i).username + "', ";
			customerLookupList += "'" + allCustomers.get(i).firstname + " " + allCustomers.get(i).lastname + "'";
			if(i != allCustomers.size()-1){
				customerLookupList += ", ";
			}
		}
		renderArgs.put("all_customers", customerLookupList);
		render();
	}
	
	//Denise
	public static void customerLookup(String keyword){
		if(keyword==null){
			params.put("outcome", "Please enter a customer name or username to lookup.");
			params.flash();
			index();		
		}else if(keyword.equals("")){
			params.put("outcome", "Please enter a customer name or username to lookup.");
			params.flash();
			index();		
		}
	
		//Find customer
		Customer customer = null;
		StringTokenizer st = new StringTokenizer(keyword);
		if(st.countTokens() == 1){
			customer = Customer.find("byUsername", st.nextToken()).first();
			if(customer==null){
				params.put("outcome", "Oops, no customer matched your search. Please try again.");
				params.flash();
				index();
			}
		}else{
			String firstName = st.nextToken();
			String lastName = st.nextToken();
			customer = Customer.find("firstname=? and lastname=?", firstName, lastName).first();
		}
		
		//Find portfolio
		List<Position> positions = Position.find("customer=?", customer).fetch();
		
		//Find transaction history
		List<Transaction> pending = Transaction.find("customer=? and execute_date is null", customer).fetch();
		List<Transaction> executed = Transaction.find("customer=? and execute_date is not null order by execute_date desc", customer).fetch();
		List<BigDecimal> prices = new ArrayList<BigDecimal>();
		for(int i=0; i<pending.size(); i++){
			if(pending.get(i).transaction_type.equalsIgnoreCase("buy") || pending.get(i).transaction_type.equalsIgnoreCase("sell")){
				String symbol = pending.get(i).position.fund.symbol;
				FundPriceHistory latest = FundPriceHistory.find("fund.symbol = ? order by price_date desc", symbol).first();
				if(latest != null){
					prices.add(latest.price);
				}else{
					prices.add(null);
				}	
			}else{
				prices.add(null);
			}
		}
		String username = Security.connected();
		Employee employee = Employee.find("byUsername", Security.connected()).first();
		BigDecimal availableBalance = CustomerArea.calculateAvailableBalance(customer);
		render(username, employee, customer, positions, pending, executed, prices, availableBalance);		
	}

	
	
	
	//XG
	public static void depositCheck(
			@Required (message="Username is required")String username, 
			@Required (message="Amount is required")String depositamount)
	{
		if(validation.hasErrors()) {
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "deposit_check"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
		}
		
		// Get the parameter to create new entry in Transaction table
		Customer customer = Customer.find("byUsername", username).first();
		if(customer != null){
			String transaction_type = "deposit";
			try{
			  BigDecimal amount = new BigDecimal(depositamount);	
			  if(amount.compareTo(BigDecimal.ZERO)<=0){
				  validation.keep(); 	 
				  validation.addError(null, "Amount is invalid", "deposit_check");
				  validation.addError(null, "Correct amount is required", "deposit_check");
				  params.put("err_type", "deposit_check"); 
				  params.flash();	
				  index();
			  } 
			  new Transaction(customer, transaction_type, amount).save();	
			} catch (Exception e){
				validation.keep();
				validation.addError(null, "Amount is invalid", "deposit_check");
				validation.addError(null, "Correct amount is required", "deposit_check"); 	
				params.put("err_type", "deposit_check"); 
				params.flash();	
				index();
			}
					
			//render(customer);
			params.put("outcome", "Deposit has been made.");
			params.flash();
			index();
		}else{
			//No such customer, throw back error msg
			validation.addError(null, "No such customer username", "username");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "deposit_check"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page					
		}
	}
	
	
	
	
	
	
	//XG
	public static void createFund(
			@Required (message="Fund name is required")String fundname, 
			@Required (message="Symbol is required")String symbol)
	{
		if(validation.hasErrors()) {
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_fund"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
		} 
	
		Fund searchFundByName = Fund.find("byName", fundname).first();
		Fund searchFundBySymbol = Fund.find("bySymbol", symbol).first();
		if(searchFundByName==null && searchFundBySymbol ==null){
			Fund fund = new Fund(fundname,symbol);
			fund.save();
		//	new FundPriceHistory(fund).save();			
			//render(fund);
			params.put("outcome", "Fund " + fundname + " has been created.");
			params.flash();
			index();
		} else {
			validation.addError(null, "Same fund has been created", "fundError");
			//render("EmployeeArea/createFund.html");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_fund"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page					
		}
	}
	
	
	
	
	
	//XG the display for transition day
	public static void transition(){
		//For getting current user -- added by Denise
		String username = Security.connected();
	
		List<FundPriceHistory> fundList = FundPriceHistory.find("byPrice_dateIsNotNull").fetch();
		int latestEntity = fundList.size()-1;
		Date lastClosingDate = fundList.get(latestEntity).price_date;		
		List<FundPriceHistory> funds = FundPriceHistory.find("price_date=?", lastClosingDate).fetch();
		//List<FundPriceHistory> newFunds = FundPriceHistory.find("byPriceIsNull").fetch();
		List<Fund> newFunds = new ArrayList<Fund>();
		List<Fund> allFunds = Fund.findAll();
		for(int i=0;i<allFunds.size();i++){
			if(FundPriceHistory.find("fund = ?", allFunds.get(i)).first() == null){
				newFunds.add(allFunds.get(i));
			} 
		}
		
		List<Transaction> pendingTransactions = Transaction.find("execute_date is NULL order by id").fetch();
		String formatedLastClosingDate = new SimpleDateFormat("yyyy-MM-dd").format(lastClosingDate);
		render(username,pendingTransactions,newFunds,funds,formatedLastClosingDate);
	}

	
	
	
	// XG  the handler for transition day
	synchronized public static void transitionDay(
			@Required String[] symbol, 
			@Required (message="Closing price is required")String[] price, 
			@Required (message="Correct date is required")Date date) 
	{
		List<FundPriceHistory> fundList = FundPriceHistory.find("byPrice_dateIsNotNull").fetch();
		int latestEntity = fundList.size()-1;
		Date lastClosingDate = fundList.get(latestEntity).price_date;
		
		if(validation.hasErrors()) {          
			validation.keep(); 	//keeps error messages for next request		
			params.flash();	
			transition();
        }
		
		// To guarantee the new date is after the last closing date
		if(date.before(lastClosingDate)||date.equals(lastClosingDate)){
			validation.addError(null, "Closing date is invalid", "dateError");
			validation.keep(); 		
			params.flash();	
			transition();
		}				
		
		
		for(int i=0;i<price.length;i++){
			// To guarantee the price is no null
			if(price[i]==null||price[i].equals("")){
				validation.addError(null, "Correct price is required", "priceError");
				validation.keep();	
				params.flash();	
				transition();
			}
						
				
			try{
				// To guarantee the price format
				BigDecimal parseToBigDecimal = new BigDecimal(price[i]);
				// To guarantee the price is more than 0
				if(parseToBigDecimal.compareTo(BigDecimal.ZERO)<=0){
					validation.addError(null, "Correct price is required", "priceError");
					validation.keep();	
					params.flash();	
					transition();
				}
			} catch (Exception e){
				validation.addError(null, "Price is invalid", "priceError");
				validation.addError(null, "Correct price is required", "priceError");
				validation.keep(); 	
				params.flash();	
				transition();
			}
		}
		
			
	
		Date executeDate = date;
		// set closing price		
		setClosingPrice(symbol, price, executeDate);
		
		// get all the elements from Transaction table
		List<Transaction> searchTransactions = Transaction.find(
				"execute_date is NULL order by id").fetch();
		if(searchTransactions ==null){ 
			validation.addError(null, "Transition Day has been submitted", "transitionError");
			transition();
		}
				
		// do the transition operations
		for (int i = 0; i < searchTransactions.size(); i++) {
			String transactionType = searchTransactions.get(i).transaction_type;
			if (transactionType.equalsIgnoreCase("deposit")) {
				Customer customer = searchTransactions.get(i).customer;
				BigDecimal amount = searchTransactions.get(i).amount;
				deposit(customer, amount);				

			}
			if (transactionType.equalsIgnoreCase("check")) {
				Customer customer = searchTransactions.get(i).customer;
				BigDecimal amount = searchTransactions.get(i).amount;
				requestCheck(customer, amount);

			}
			if(transactionType.equalsIgnoreCase("buy")){
				Customer customer = searchTransactions.get(i).customer;
				BigDecimal amount = searchTransactions.get(i).amount;
				Fund fund = searchTransactions.get(i).position.fund;
				BigDecimal closingPrice = getClosingPrice(fund, executeDate);
				buy(customer, fund, amount);
				// update position table
				if(searchTransactions.get(i).position.customer ==null){
					// this customer buy a new fund
					// add Customer and Shares into particular row of position table
					searchTransactions.get(i).position.customer = searchTransactions.get(i).customer;
					searchTransactions.get(i).position.shares = amount.divide(closingPrice, 2, BigDecimal.ROUND_HALF_UP); // to guarantee 1/3
					searchTransactions.get(i).position.save();
					
				} else{
					// this customer buy more shares based on his/her old fund
					BigDecimal newShares = amount.divide(closingPrice, 2, BigDecimal.ROUND_HALF_UP); // to guarantee 1/3
					searchTransactions.get(i).position.shares = searchTransactions.get(i).position.shares.add(newShares);
					searchTransactions.get(i).position.save();
					
				}
				searchTransactions.get(i).shares = amount.divide(closingPrice, 2, BigDecimal.ROUND_HALF_UP); 
				searchTransactions.get(i).save();
								
			}
			if(transactionType.equalsIgnoreCase("sell")){
				Customer customer = searchTransactions.get(i).customer;
				Fund fund = searchTransactions.get(i).position.fund;
				BigDecimal shares = searchTransactions.get(i).shares;
				BigDecimal closingPrice = getClosingPrice(fund, executeDate);
				BigDecimal currentShares = searchTransactions.get(i).position.shares;
				BigDecimal sellShares = searchTransactions.get(i).shares;
				sell(customer, fund, sellShares, closingPrice);
				BigDecimal newShares = currentShares.subtract(sellShares);
				// update position table
				if(newShares.equals(BigDecimal.ZERO)){
					// if the customer sell all delete the row from position table
					searchTransactions.get(i).position.shares = null;
					searchTransactions.get(i).position.customer = null;
					searchTransactions.get(i).position.save();
					//searchTransactions.get(i).position.delete();
					searchTransactions.get(i).position.save();
				} else{
					searchTransactions.get(i).position.shares = newShares;
					searchTransactions.get(i).position.save();
				}
				
				List<Position> zeroShares = Position.find("shares = ?",BigDecimal.ZERO).fetch();
				for(int setNull=0;setNull<zeroShares.size();setNull++){
					zeroShares.get(setNull).customer = null;
					zeroShares.get(setNull).shares = null;
				}
				
			}
			// update the date		
			searchTransactions.get(i).execute_date = executeDate;
			searchTransactions.get(i).save();
		}
		
		
		List<Position> positionList = Position.findAll();
		BigDecimal tempShares;
		Customer tempCustomer;
		Fund tempFund;
		for(int out=0;out<positionList.size()-1;out++){
			tempShares = positionList.get(out).shares;
			tempCustomer = positionList.get(out).customer;
			tempFund = positionList.get(out).fund;
			for(int in=out+1;in<=positionList.size()-1;in++){
				if(positionList.get(in).customer == positionList.get(out).customer && positionList.get(in).fund == positionList.get(out).fund && positionList.get(in).shares !=null){
					tempShares = tempShares.add(positionList.get(in).shares);
					positionList.get(in).shares = null;
					positionList.get(in).customer = null;
					positionList.get(in).save();
				}
			}
			positionList.get(out).shares = tempShares;
			positionList.get(out).save();
		}
		index();
	}
	
	
	
	
	//EG
	public static void createEmployee() {
		render();
	}
	
	//EG
	public static void createEmployeeHandler(
			@Required(message="Username is required")String username, 
			@Required(message="Password is required") String password, 
			@Required(message="First Name is required")String firstname, 
			@Required(message="Last Name is required")String lastname
	){
		Customer customer = Customer.find("byUsername", username).first();
		if (customer != null) {
			validation.addError(username, "Username already exists.", username);
		}        
		
		if(validation.hasErrors()) {
            //render("EmployeeArea/createEmployee.html");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_emp"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
        }
		
		Employee employee = new Employee(username, password, firstname, lastname);
		try {
			employee.save();
		} catch (PersistenceException p) {
			validation.addError(null, "Username already exists", "username");
			//render("EmployeeArea/createEmployee.html");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_emp"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
		}		
		
		//renderText("Employee " + username + " created successfuly.");
		params.put("outcome", "Employee account has been created.");
		params.flash();
		index();		
	}
	
	//EG
	public static void createCustomer() {
		render();
	}
	
	//EG
	public static void createCustomerHandler(
			@Required(message="Username is required")String username, 
			@Required(message="Password is required") String password, 
			@Required(message="First Name is required")String firstname, 
			@Required(message="Last Name is required")String lastname,
			@Required(message="Address1 is required")String addr_line1,
			String addr_line2,
			@Required(message="City is required")String city,
			@Required(message="State is required")String state,
			@Required(message="Zip code is required")String zip,
			@Required(message="Cash is required")String cashString //@Numeric()? 
	){
		
		BigDecimal cash = null;
				
		try {
			cash = new BigDecimal(cashString);
		} catch (NumberFormatException n) { 
			validation.addError(null, "Cash must be numeric", "cashString");
		}
				
		if(validation.hasErrors()) {
            //render("EmployeeArea/createCustomer.html");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_cust"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page
        }
		
		Employee employee = Employee.find("byUsername", username).first();
		if (employee != null) {
			validation.addError(username, "Username already exists.", username);
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_cust"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page
		}
		
		try {
			Customer customer = new Customer(username, password, firstname, lastname, addr_line1, addr_line2, city, state, zip, cash);
			customer.save();		
		} catch (PersistenceException p) {
			validation.addError(null, "Username already exists", "username");
			//render("EmployeeArea/createCustomer.html");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "create_cust"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page
		}	
		
		params.put("outcome", "Customer account has been created.");
		params.flash();
		index();
	}
	
	//EG
	public static void changeMyPassword() {
		String username = Security.connected();
		Employee employee = Employee.find("byUsername", username).first();
		String firstname = employee.firstname;
		String lastname = employee.lastname;
		render(username, firstname, lastname);
	}
	
	//EG
	public static void changeMyPasswordHandler(
			@Required(message="Current password is required")String currentPassword,
			@Required(message="New password is required")String newPassword,
			@Required(message="Retyped new password is required")String retypeNewPassword
			){
		String username = Security.connected();
		Employee employee = Employee.find("byUsername", username).first();
		String firstname = employee.firstname;
		String lastname = employee.lastname;
		
		if (!newPassword.equals(retypeNewPassword)) {
			validation.addError(null, "New passwords do not match.", "newPassword");
		}		
		
		if(validation.hasErrors()) {
			render("EmployeeArea/changeMyPassword.html", username, firstname, lastname);
			return;
		}		
		
		if (!currentPassword.equals(employee.password)) {
			validation.addError(null, "Incorrect current password.", "currentPassword");
			render("EmployeeArea/changeMyPassword.html", username, firstname, lastname);
			return;			
		}
			
		employee.password = newPassword;
		employee.save();
		
		params.put("outcome", "Your password has been successfully updated.");
		params.flash();
		changeMyPassword();
	}
	
	//EG
	public static void resetCustomerPassword() {
		render();
	}	
	
	//EG
	public static void resetCustomerPasswordHandler(
		@Required(message="Username is required")String username
	){
		if(validation.hasErrors()) {
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "reset_pwd"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
        }
		
		String random_password = Long.toHexString(Double.doubleToLongBits(Math.random())).substring(0,6);
		Customer customer = Customer.find("byUsername", username).first();
		try {
			customer.password = random_password;
		} catch (NullPointerException n) {
			validation.addError(null, "Customer username not found!", "username");
			validation.keep(); 	//keeps error messages for next request
			params.put("err_type", "reset_pwd"); //for frontend to know which dialog had errors
			params.flash();		//keeps param values from form to repopulate
			index();			//load back index page			
		}
		customer.save();
		
		params.put("outcome", "Customer " + username + "'s password has been reset to: " + random_password);
		params.flash();
		index();		
	}
	
	//EG
	public static void viewCustomerAccount() {
		render();
	}
	
	//Denise
	public static void customerLookup(){
		render();
	}
	
	
	/**
	 * To calculate the "Deposit" operation
	 * 
	 * @param customer
	 * @param amount
	 */
	private static void deposit(@Required Customer customer, BigDecimal amount) {
		try{
		   customer.cash = customer.cash.add(amount);
		   customer.save();
		} catch (Exception e){ 
			validation.addError(null, "Incorrect data on deposit operation", "depositError");
			validation.keep();
			transition();
		}
	}

	/**
	 * To calculate the "Request Check" operation
	 * 
	 * @param customer
	 * @param amount
	 */
	private static void requestCheck(@Required Customer customer,
			BigDecimal amount) {
		try{
		customer.cash = customer.cash.subtract(amount);
		customer.save();
		} catch (Exception e){
			validation.addError(null, "Incorrect date on requesting check operation", "requestCheckError");
			validation.keep();
			transition();
		}
	}

	/**
	 * To calculate the "Buy" operation
	 * 
	 * @param customer
	 * @param fundsName
	 * @param amount
	 */
	private static void buy(@Required Customer customer, Fund fund,
			BigDecimal amount) {
		try{
		customer.cash = customer.cash.subtract(amount);
		customer.save();
		} catch (Exception e){
			validation.addError(null, "Incorrect date on buying operation", "buyError");
		}
	}

	/**
	 * To calculate the "Sell" operation
	 * 
	 * @param customer
	 * @param fundsName
	 * @param shares
	 * @param closingPrice
	 */
	private static void sell(@Required Customer customer, Fund fund,
			BigDecimal shares, BigDecimal closingPrice) {
		try{
		customer.cash = customer.cash.add(shares.multiply(closingPrice));
		customer.save();
		} catch (Exception e ){
			validation.addError(null, "Incorrect data on selling operation", "sellError");
			validation.keep();
			transition();
		}

	}
	
	/**
	 * To update the closing price
	 * @param symbol
	 * @param price
	 */
	private static void setClosingPrice(@Required String[] symbol, String[] price, Date executeDate){
	//	List<FundPriceHistory> newFunds = FundPriceHistory.find("byPriceIsNull").fetch();
	/*
		for(int m=0;m<newFunds.size();m++){
		  newFunds.get(m).price = BigDecimal.ONE;
		  newFunds.get(m).save();
		}
	*/
		try{
		  for(int i =0;i<symbol.length;i++){
			  Date date = executeDate;
			  BigDecimal closingPrice = new BigDecimal(price[i]);
			  Fund fund = Fund.find("bySymbol", symbol[i]).first();			
			  new FundPriceHistory(fund, date, closingPrice).save();
			
		  }
		} catch (Exception e){
			validation.addError(null, "Incorrect data on setting closing price operation", "setClosingPriceError");
			validation.keep();
			transition();
		}
	}
	
	/**
	 * To get the closing price
	 * @param fund
	 * @param executeDate
	 * @return
	 */
	private static BigDecimal getClosingPrice(@Required Fund fund, Date executeDate){
		FundPriceHistory fundPriceHistory = FundPriceHistory.find("fund =? and price_date =?", fund, executeDate).first();
		return fundPriceHistory.price;
	}
}
